package com.efrei.uchat.client;

import java.io.IOException;
import java.net.Socket;
import java.util.Observable;
import java.util.Observer;

import org.apache.log4j.Logger;

import com.efrei.uchat.exception.UChatException;
import com.efrei.uchat.io.MessageSender;
import com.efrei.uchat.message.CroomMessage;
import com.efrei.uchat.message.CroomUsersMessage;
import com.efrei.uchat.message.CroomsMessage;
import com.efrei.uchat.message.Headers;
import com.efrei.uchat.message.LoginMessage;
import com.efrei.uchat.message.Message;
import com.efrei.uchat.message.TextMessage;
import com.efrei.uchat.model.croom.CroomMessageStatus;

/**
 * UChat client's root class
 */
public class UChatClient extends Observable implements Observer {
	
	private static Logger logger = Logger.getLogger(ServerListener.class);
	
	private Socket client;
	
	private ServerListener listener;
	private int sessionId = 0;
	private String login;

	public enum UChatStatus {
		/**
		 * connection init with login
		 */
		INIT,
		/**
		 * waiting client's messages
		 */
		WAITING_MESSAGE,
		/**
		 * server disconnected
		 */
		DISCONNECT
	};
	private UChatStatus status = UChatStatus.INIT;
	
	public UChatClient(String host, int port) throws IOException {
		this.client = new Socket(host, port);
		status = UChatStatus.INIT;
		
		// listen server
		listener = new ServerListener(client);
		listener.addObserver(this);
		new Thread(listener).start();
	}

	protected void send(final Message message) throws UChatException {
		if(status == UChatStatus.DISCONNECT) {
			throw new UChatException("invalid status for send message (id:"+
					message.getSessionId()+"), "+this);
		}
		try {
			new Thread(new MessageSender(this, client, message)).start();
		} catch (IOException e) {
			logger.error("ERROR with send: ");
			e.printStackTrace();
			closeConnection();
		}
	}
	
	public void closeConnection() {
		if(status == UChatStatus.DISCONNECT) {
			return;
		}
		status = UChatStatus.DISCONNECT;
		
		logger.info("close socket and notify observers with null");
		try {
			client.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

		// notification to listeners
		setChanged();
		notifyObservers(null);
		deleteObservers();
	}

	@Override
	public void update(Observable o, Object arg) {
		if(o instanceof ServerListener) {
			// SERVER'S MESSAGES
			if(arg==null) {
				// disconnect message
				logger.info("INFO: ServerListener send null");
				closeConnection();
				return;
			}
			try {
				manageMessage((Message)arg);
			} catch (UChatException e) {
				e.printStackTrace();
				closeConnection();
			}
		} else if(o instanceof MessageSender) {
			//Exception error = (Exception) arg;
			// error while sending message: disconnect
			logger.error("INFO: MessageSender failed to send "+arg.toString());
			closeConnection();
		} else if(o instanceof UChatUI) {
			// UI'S MESSAGES
			try {
				Message message = generateMessageRequest((RequestMessage) arg);
				if(message!=null)
					send(message);
			} catch (UChatException e) {
				logger.error("error with message sending: "+e);
				closeConnection();
			} catch (Exception e) {
				logger.error("error with message sending: "+e);
				closeConnection();
			}
		}
	}

	/**
	 * generate Message indicated in request
	 * @param request
	 * @return message or null if request unsupported
	 * @throws UChatException
	 */
	private Message generateMessageRequest(RequestMessage request) throws UChatException {
		Message message = null;
		Object[] args = request.getArgs();
		
		switch (request.getType()) {
		case TEXT_CHANNEL:
			// message to croom
			// args = croom + text
			message = new TextMessage(
					args[1].toString(),
					args[0].toString(),
					sessionId, login,
					sessionId);
			break;
		case CROOM_MESSAGE:
			// croom request
			// args = status + croom + password
			String password = (args[2]==null?null:args[2].toString());
			message = new CroomMessage(
					(CroomMessageStatus) args[0],
					args[1].toString(),
					password,
					sessionId);
			break;
		case CROOM_LIST_MESSAGE:
			message = new CroomsMessage(sessionId);
			break;
		case CROOM_USERS_MESSAGE:
			// croom's users
			// args = croom
			message = new CroomUsersMessage(
					sessionId,
					args[0].toString());
			break;
		case LOGIN:
			// rename request
			// args = login
			message = new LoginMessage(
					args[0].toString(),
					sessionId);
			break;
		default:
			logger.error(
					"ERROR: unsupported message in UChatClient for "+
							request.getType().getText());
		}
		return message;
	}

	/**
	 * manage message received from ServerListener
	 * @param message
	 * @throws UChatException
	 */
	private void manageMessage(Message message) throws UChatException {
		switch (status) {
		case INIT:
			if(message.getType()==Headers.LOGIN) {
				LoginMessage login = (LoginMessage) message;
				// take sessionId if not known
				if(sessionId != login.getSessionId()) {
					sessionId = login.getSessionId();
				}
				if(login.isValid() && getStatus()!=UChatStatus.DISCONNECT) {
					setStatus(UChatStatus.WAITING_MESSAGE);
				} else {
					logger.error("LOGIN failed or status==disconnect, retry");
				}
			}
			break;
		default:
			// WAITING_MESSAGE status
			if(message.getType()==Headers.LOGIN) {
				LoginMessage msg = (LoginMessage) message;
				if(msg.isValid()) {
					this.login = msg.getLogin();
				}
			}
			break;
		}
		// notification to listeners
		setChanged();
		notifyObservers(message);
	}
	
	/**
	 * @return login update when rename are accepted
	 */
	public String getLogin() {
		return login;
	}
	
	public void setLogin(String login) {
		this.login = login;
	}
	
	public UChatStatus getStatus() {
		synchronized (status) {
			return status;
		}
	}
	
	public void setStatus(UChatStatus status) {
		synchronized (status) {
			this.status = status;
		}
	}
}
